package com.zxshhy.controller;

import com.alibaba.fastjson.JSONObject;
import com.zxshhy.BalanceTCCServiceFeign;
import com.zxshhy.StorageTCCServiceFeign;
import com.zxshhy.entity.Order;
import com.zxshhy.service.OrderTCCService;
import com.zxshhy.util.UUIDGenerator;
import com.zxshhy.vo.OrderVo;
import com.zxshhy.vo.ResultVo;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @author zxs
 */
@RefreshScope
@RestController
@RequestMapping("/tcc/order")
@Slf4j
public class OrderTCCController {

    @Value("${zxshhy.seata.data-source-proxy-mode:XXXX}")
    private String trxMode;

    @Resource
    private OrderTCCService orderTCCService;

    @Resource
    private BalanceTCCServiceFeign balanceTCCServiceFeign;

    @Resource
    private StorageTCCServiceFeign storageTCCServiceFeign;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 采用二阶段提交XA，AT强一致性实现分布式事务
     * @param orderVo
     * @return
     */
    @Transactional
    @GlobalTransactional(name="createOrderTCC",rollbackFor=Exception.class)
    @PostMapping("/createOrderTCC")
    public ResultVo createOrderTCC(@RequestBody OrderVo orderVo){
        log.info("收到下单请求,用户:{}, 商品编号:{},trxMode={}", orderVo.getUserId(), orderVo.getCommodityCode(),trxMode);
        log.info("当前 XID: {}", RootContext.getXID());
        //获取全局唯一订单号  测试使用
        Long orderId = UUIDGenerator.generateUUID();
        //阶段一： 创建订单
        Order order = orderTCCService.prepareSaveOrder(orderVo,orderId);
        //扣减库存
        Boolean storage_isSuccess = storageTCCServiceFeign.deduct(orderVo.getCommodityCode(), orderVo.getCount());
        log.info("扣减库存[{}]：{}",orderVo.getCount(),storage_isSuccess?"成功":"失败");
        //扣减余额
        Boolean balance_isSuccess = balanceTCCServiceFeign.debit(orderVo.getUserId(), orderVo.getMoney());
        log.info("扣减账户余额[{}]：{}",orderVo.getMoney(),balance_isSuccess?"成功":"失败");
        return ResultVo.ok().put("order",order);
    }

    /**
     * 原理：首先获取到锁（这个锁30s后自动释放），然后对锁设置一个延迟任务（10s后执行），延迟任务给锁的释放时间刷新为30s，
     * 并且还为锁再设置一个相同的延迟任务（10s后执行），这样就达到了如果一直不释放锁（程序没有执行完）的话，看门狗机制会每10s将锁的自动释放时间刷新为30s。
     * 而当程序出现异常，那么看门狗机制就不会继续递归调用renewExpiration，这样锁会在30s后自动释放。
     * 1. 在获取锁的时候，不能指定`leaseTime`或者只能将`leaseTime`设置为-1，这样才能开启看门狗机制。
     * 2. 在`tryLockInnerAsync`方法里尝试获取锁，如果获取锁成功调用`scheduleExpirationRenewal`执行看门狗机制
     * 3. 在`scheduleExpirationRenewal`中比较重要的方法就是`renewExpiration`，当线程第一次获取到锁（也就是不是重入的情况），那么就会调用`renewExpiration`方法开启看门狗机制。
     * 4. 在`renewExpiration`会为当前锁添加一个延迟任务`task`，这个延迟任务会在10s后执行，执行的任务就是将锁的有效期刷新为30s（这是看门狗机制的默认锁释放时间）
     * 5. 并且在任务最后还会继续递归调用`renewExpiration`
     * @param orderVo
     * @return
     */
    @PostMapping("/createOrderByRedission")
    public ResultVo createOrderByRedission(@RequestBody OrderVo orderVo){
        log.info("redisson的看门狗线程原理:{}", JSONObject.toJSONString(orderVo));
        RLock rLock = redissonClient.getLock("order:create:userid:" + orderVo.getUserId());
        boolean isLock = false;
        try {
            //注意：在获取锁的时候，不能指定`leaseTime`或者只能将`leaseTime`设置为-1，这样才能开启看门狗机制
            //waitTime：获取锁的最大等待时间
            //leaseTime：获取锁成功的持有时间，过期自动解锁,不指定解锁时间，则会开启看门狗，超过30秒自动续租锁时间，看门狗延迟任务每10秒执行一次
            //unit：时间单位
            isLock = rLock.tryLock(1,10,  TimeUnit.SECONDS);
            if (isLock) {
                log.info("============实现自己业务代码============");
//                CreateOrderResp rv = orderBizService.createOrder(businessId, customerId, userId, createOrderForm);
//                orderLifecycleService.handleCreateOrder(rv.getOrderNo());
                if("111".equals(orderVo.getCommodityCode())){
//                    Thread.sleep(15000);
                }
                return ResultVo.ok(String.join("","【已获取到分布式锁：",rLock.getName(),"】看门狗原理:首先获取到锁（这个锁30s后自动释放），然后对锁设置一个延迟任务（10s后执行），延迟任务给锁的释放时间刷新为30s，并且还为锁再设置一个相同的延迟任务（10s后执行），这样就达到了如果一直不释放锁（程序没有执行完）的话，看门狗机制会每10s将锁的自动释放时间刷新为30s。\n" + "而当程序出现异常，那么看门狗机制就不会继续递归调用renewExpiration，这样锁会在30s后自动释放"));
            } else {
                return ResultVo.ok(String.join("","此分布式锁已被占用：",rLock.getName()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (isLock) {
                rLock.unlock();
            }
        }
        return ResultVo.ok(String.join("","看门狗原理:首先获取到锁（这个锁30s后自动释放），然后对锁设置一个延迟任务（10s后执行），延迟任务给锁的释放时间刷新为30s，并且还为锁再设置一个相同的延迟任务（10s后执行），这样就达到了如果一直不释放锁（程序没有执行完）的话，看门狗机制会每10s将锁的自动释放时间刷新为30s。\n" + "而当程序出现异常，那么看门狗机制就不会继续递归调用renewExpiration，这样锁会在30s后自动释放"));
    }



}
